<?php 
/**
 * This file is part of DomSql.
 * 
 * DomSql is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * DomSql is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with DomSql.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */

/**
 * Query object for building SQL queries.
 * Sample:
 * <code>
 * $result = DomSqlQuery::c()
 * 		->select('t.id,t.name')
 * 		->from('myTable')
 * 		->where('t.id > 20')
 * 		->limit(4)
 * 		->commit();
 * foreach( $result as $row ) {
 * 		//...
 * }
 * </code>
 * 
 * @package DomSql
 */ 
class DomSqlQuery {
	
	const DEFAULT_WHERE_GROUP = 0;

	/**
	 * Connection alias
	 * @var String
	 */
	protected $alias = DomSql::DEFAULT_ALIAS;
	/**
	 * Fields that are used by this query
	 *
	 * @var Array
	 */
	protected $fields = array();
	/**
	 * Joins that are made
	 *
	 * @var Array
	 */
	protected $joins = array();
	/**
	 * Use DISTINCT on select query
	 *
	 * @var Bool
	 */
	protected $distinct = false;
	/**
	* @param Array main table info
	* Keys: tableName=>String , alias=>Strings 
	*/
	protected $from = array();
	/**
	 * Queries where clauses
	 *
	 * @var Array
	 */
	protected $where = array();
	/**
	 * Where groups
	 *
	 * @var Array
	 */
	protected $whereGroups = array();
	/**
	 * Whether to use SQL_CALC_FOUND_ROWS(true) or send count(*)(false) query to count all possible matches.
	 * 
	 * @var Bool
	 */
	protected $sqlCalcFoundRows;
	/**
	 * Order by part
	 *
	 * @var String
	 */
	protected $orderBy;
	/**
	 * Limit part
	 *
	 * @var String
	 */
	protected $limit;
	/**
	 * Result set index/key column name.
	 *
	 * @var String
	 */
	protected $indexColumnName = NULL;
	/**
	 * Result set value column name
	 *
	 * @var String
	 */
	protected $valueColumnName = NULL;
	
	public function __construct() {
		
	}
	/**
	 * @return DomSqlQuery
	 */
	public static function c() {
		return new DomSqlQuery;
	}
	/**
	 * @param String $alias
	 * @return DomSqlQuery
	 */
	public function setAlias( $alias ) {
		$this->alias = $alias;
		return $this;
	}
	/**
	 * This column value is returned as key. (Key's don't have to be unique, you will still get all results!)
	 * @var String
	 * @return DomSqlQuery
	 */
	public function setIndexColumnName( $indexColumnName ) {
		$this->indexColumnName = $indexColumnName;
		return $this;
	}
	/**
	 * This column value is returned as value.
	 * @var String
	 * @return DomSqlQuery
	 */
	public function setValueColumnName( $valueColumnName ) {
		$this->valueColumnName = $valueColumnName;
		return $this;
	}
	/**
	 * Arguments can be given as seperate params.
	 * <code>
	 * $query->select('t.id, t.name as itemName');
	 * $query->select('t.id','t.name as itemName');
	 * $query->select('t.id',array('t.name','itemname'))
	 * </code>
	 * @return DomSqlQuery
	 */
	public function select() {
		foreach( func_get_args() as $arg) {
			if( is_string($arg) === true )
				$this->fields[$arg] = $arg;
			else if( is_array($arg) === true )
				$this->fields[$arg[1]] = (string)($arg[0]).' as '.$arg[1];
			else
				throw new InvalidArgumentException('Unknown field type!');
		}
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function distinct( $value = true ) {
		$this->distinct = (bool)$value;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function from( $tableName, $as = 't' ) {
		//if( $this->from === array() ) {
			$this->from = array( 'from' => $tableName, 'alias'=>$as);
			return $this;
		//} else
		//	throw new BadMethodCallException('Theres table already specified!');
	}
	/**
	* <code>
	* $q->where('t.id = 10')
	* $q->where('t.id = 10 OR t.id = 12'); // 10 or 12
	* $q->where('t.id  = 10')->where('t.id = 12'); // 10 and 12
	* $q->where('t.id  = 10')->whereOr('t.id = 12'); // 10 or 12
	* $q->where('t.id = 10', 'g')->whereOr('t.id = 12', 'g')->where('t.id = 13'); // (10 OR 12) AND 13 
	* $q->where('t.id = 10', 'g')->whereOr('t.id = 12', 'g')->where('t.id = 13')->where('t.id = 14'); // (10 OR 12) AND (13 AND 14)	
	* $q->whereOr('t.id = 10', 'g')->whereOr('t.id = 12', 'g')->whereOr('t.id = 13')->whereOr('t.id = 14'); // (10 OR 12) AND (13 OR 14)
	* </code>
	* @return DomSqlQuery	
	*/
	public function where( $where, $group = self::DEFAULT_WHERE_GROUP, $groupLogic = 'AND' ) {
		if( $this->where[$group] ) {
			$this->where[$group] .= ' AND '. $where;
		} else {
			$this->where[$group] = $where;
			$this->whereGroups[$group] = $groupLogic;
		}
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function whereOr( $where, $group = self::DEFAULT_WHERE_GROUP, $groupLogic = 'AND' ) {
		if( $this->where[$group] ) {
			$this->where[$group] .= ' OR '. $where;
		} else {
			$this->where[$group] = $where;
			$this->whereGroups[$group] = $groupLogic;
		}
		return $this;
	}
	/**
	 * Performs fulltext search. See Mysql manual about FULLTEXT searches.
	 * If "as" is added, MATCH .. AGAINST  part is also put into select part.
	 *
	 * @param String $columns
	 * @param String $against
	 * @param String $as
	 * @param String $mode
	 * @return DomSqlQuery
	 */
	public function matchAgainst( $columns, $against, $as = NULL, $mode = '' ) {
		$str = 'MATCH ('.$columns.') AGAINST (\''.$against.'\' '.$mode.')';
		if( $as !== NULL)
			$this->select( $str.' AS '.$as );
		$this->where( $str );
		return $this;
	}
	/**
	 * @param String $tableName
	 * @param String $as
	 * @param String $criteria ON clause
	 * @param Bool $shift Add at the beginning of joins
	 * @param String $type [JOIN|LEFT JOIN|RIGHT_JOIN]
	 * @return DomSqlQuery
	 */
	public function join( $tableName, $as, $criteria, $shift = false, $type = 'JOIN' ) {
		if( $this->from['alias'] != $as ) {
			if( $shift === false ) {
				$this->joins[$as] = array('type'=>$type, 'tableName'=>$tableName, 'alias'=>$as, 'criteria'=>$criteria);
			} else {
				$this->joins = array_merge( array($as=>array('type'=>$type, 'tableName'=>$tableName, 'alias'=>$as, 'criteria'=>$criteria)), $this->joins);
			}
		}
		return $this;
	}
	/**
	* Alias for join
	* @return DomSqlQuery
	*/
	public function leftJoin( $tableName, $as, $criteria, $shift = false ) {
		$this->join( $tableName, $as, $criteria, $shift, 'LEFT JOIN' );
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function rightJoin( $tableName, $as, $criteria, $shift = false ) {
		$this->join( $tableName, $as, $criteria, $shift, 'RIGHT JOIN' );
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function countFoundRows( $sqlCalcFoundRows = false ) {
		$this->sqlCalcFoundRows = (bool)$sqlCalcFoundRows;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function orderBy( $orderBy ) {
		$this->orderBy = $orderBy;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function limit( $limit ) {
		$this->limit = $limit;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function groupBy( $groupBy ) {
		$this->groupBy = $groupBy;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function having( $having ) {
		$this->having = $having;
		return $this;
	}
	/**
	 * @return String
	 */
	public function toCountString() {
		
		$query = 'SELECT '.($this->groupBy ? '1' : 'count(*)').' as n';
		$query.= ' FROM ' . $this->from['from'].' as '.$this->from['alias'];
		
		foreach( $this->joins as $join ) {
			$query .= ' '.$join['type'].' '.$join['tableName'].' as '.$join['alias'].' ON ('.$join['criteria'].')';
		}
		
		if( $this->where ) {
			$where = '';
			foreach( $this->where as $group => $sql )
				$where .= ($where ? $this->whereGroups[$group] : NULL).' ('.$sql.')';
			$query .= ' WHERE '.$where;
		}
		
		if( $this->groupBy )
			$query .= ' GROUP BY '.$this->groupBy;
			
		if( $this->having ) {
			throw new RuntimeException('DomSqlQuery does not suppoert countQuery where original query has HAVING part in it!');
		}
		
		if( $this->groupBy )
			$query = 'SELECT SUM(n) as n FROM ('.$query.') as f';
		return $query;
	}
	
	/**
	 * @return String
	 */
	public function toString() {
		$query = 'SELECT '.($this->distinct ? 'DISTINCT ' : NULL) . ($this->sqlCalcFoundRows ? 'SQL_CALC_FOUND_ROWS ' : NULL);
		
		if( $this->fields == false )
			$query .= '* ';
		else
			$query .= implode(',',$this->fields).' ';
		
		$query.= 'FROM ' . $this->from['from'].' as '.$this->from['alias'];
		
		foreach( $this->joins as $join ) {
			$query .= ' '.$join['type'].' '.$join['tableName'].' as '.$join['alias'].' ON ('.$join['criteria'].')';
		}
		
		if( $this->where ) {
			$where = '';
			foreach( $this->where as $group => $sql )
				$where .= ($where ? $this->whereGroups[$group] : NULL).' ('.$sql.')';
			$query .= ' WHERE '.trim($where);
		}
		
		if( $this->groupBy )
			$query .= ' GROUP BY '.$this->groupBy;
			
		if( $this->having )
			$query .= ' HAVING '.$this->having;
		
		if( $this->orderBy )
			$query .= ' ORDER BY '.$this->orderBy;
			
		if( $this->limit )
			$query .= ' LIMIT '.$this->limit;
			
		return $query;
	}
	/**
	 * @return DomSqlQueryIterator
	 */
	public function getIterator() {
		return new DomSqlQueryIterator( $this, $this->alias );
	}
	/**
	 * DomSqlQueryIterator is returned when limit is not 1.
	 * Row is returned when limit === 1
	 * @return Mixed
	 */
	public function execute() {
		if( $this->limit === 1) {
			$i = $this->getIterator();
			$i->rewind();
			return $i->current();
		} else {
			return $this->getIterator();
		}
	}
	
	/**
	* Alias for execute.
	*/
	public function commit() {
		return $this->execute();
	}
	
	/**
	 * @deprecated 
	 * @return DomSqlQuery
	 */
	public function flushFields() {
		$this->fields = array();
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flushColumns() {
		$this->flustFields();
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flushJoins() {
		$this->joins = array();
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flushFrom() {
		$this->from = array();
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flushWhere() {
		$this->where = NULL;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flushOrderBy() {
		$this->orderBy = NULL;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flushLimit() {
		$this->limit = NULL;
		return $this;
	}
	/**
	 * @return DomSqlQuery
	 */
	public function flush() {
		$this->flushFields();
		$this->flushJoins();
		$this->flushWhere();
		$this->flushOrderBy();
		$this->flushLimit();
		return $this;
	}
	/**
	 * @param String $var
	 * @return Mixed
	 */
	public function __get( $var ) {
		return $this->$var;
	}
	/**
	 * @return String
	 */
	public function __toString() {
		return $this->toString();
	}

}
?>